#include <kernel/kobject.h>
#include <kernel/initcall.h>
#include <kernel/syslog.h>
#include <mm/mem.h>
#include <drivers/platform.h>
#include <drivers/spi.h>
#include <rp2040.h>
#include <kernel/fdt.h>
#include <iofunc.h>
#include "hardware/rp2040_spi.h"
#include "hardware/rp2040_clocks.h"


#define RP2040_SPI_FIFOSZ        8
#include "rp2040_spi.h"


static inline uint32_t spi_getreg(uint32_t io_base, uint32_t offset)
{
    return *(volatile uint32_t *)(io_base + offset);
}

static inline void spi_putreg(uint32_t io_base, uint32_t offset, uint32_t value)
{
    *(volatile uint32_t *)(io_base + offset) = value;
}


static void spi_setfrequency(uint32_t io_base, uint32_t frequency)
{
    uint32_t divisor;
    /* frequency = SPI_CLOCK / divisor, or divisor = SPI_CLOCK / frequency */
    divisor = 133000000 / frequency;

    /* "In master mode, CPSDVSRmin = 2 or larger (even numbers only)" */

    if (divisor < 2)
    {
        divisor = 2;
    }
    else if (divisor > 254)
    {
        divisor = 254;
    }

    divisor = (divisor + 1) & ~1;

    /* Save the new divisor value */

    spi_putreg(io_base, RP2040_SPI_SSPCPSR_OFFSET, 2);
}


static void spi_setmode(uint32_t io_base, uint8_t mode)
{
    uint32_t regval;

    /* Has the mode changed? */

    regval = spi_getreg(io_base, RP2040_SPI_SSPCR0_OFFSET);
    regval &= ~(RP2040_SPI_SSPCR0_SPO | RP2040_SPI_SSPCR0_SPH);

    switch (mode)
    {
        case 0: /* CPOL=0; CPHA=0 */
        break;

        case 1: /* CPOL=0; CPHA=1 */
        regval |= RP2040_SPI_SSPCR0_SPH;
        break;

        case 2: /* CPOL=1; CPHA=0 */
        regval |= RP2040_SPI_SSPCR0_SPO;
        break;

        case 3: /* CPOL=1; CPHA=1 */
        regval |= (RP2040_SPI_SSPCR0_SPO | RP2040_SPI_SSPCR0_SPH);
        break;
    }

    spi_putreg(io_base, RP2040_SPI_SSPCR0_OFFSET, regval);
}


static void spi_setbits(uint32_t io_base, int nbits)
{
    uint32_t regval;
    /* Yes... Set CR0 appropriately */

    regval = spi_getreg(io_base, RP2040_SPI_SSPCR0_OFFSET);
    regval &= ~RP2040_SPI_SSPCR0_DSS_MASK;
    regval |= ((nbits - 1) << RP2040_SPI_SSPCR0_DSS_SHIFT);
    spi_putreg(io_base, RP2040_SPI_SSPCR0_OFFSET, regval);
}


static uint32_t spi_send(uint32_t io_base, uint32_t wd)
{
    register uint32_t regval;

    /* Wait while the TX FIFO is full */

    while (!(spi_getreg(io_base, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_TNF)) ;

    /* Write the byte to the TX FIFO */

    spi_putreg(io_base, RP2040_SPI_SSPDR_OFFSET, wd);

    /* Wait for the RX FIFO not empty */

    while (!(spi_getreg(io_base, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE))
    ;

    /* Get the value from the RX FIFO and return it */

    regval = spi_getreg(io_base, RP2040_SPI_SSPDR_OFFSET);
    return regval;
}


void rp2040_spibus_initialize(uint32_t base)
{
  register uint32_t regval;
  /* Configure 8-bit SPI mode */

  spi_putreg(base, RP2040_SPI_SSPCR0_OFFSET,
             ((8 - 1) << RP2040_SPI_SSPCR0_DSS_SHIFT) |
             (0 << RP2040_SPI_SSPCR0_FRF_SHIFT));

  /* Disable SPI and all interrupts (we'll poll for all data) */

  spi_putreg(base, RP2040_SPI_SSPCR1_OFFSET, 0);
  spi_putreg(base, RP2040_SPI_SSPIMSC_OFFSET, 0);

  /* Clear interrupts */

  spi_putreg(base, RP2040_SPI_SSPICR_OFFSET, 0x3);

  /* Set the initial SPI configuration */

  /* Select a default frequency of approx. 400KHz */

  spi_setfrequency(base, 133000000);

  regval = spi_getreg(base, RP2040_SPI_SSPCR1_OFFSET);
  spi_putreg(base, RP2040_SPI_SSPCR1_OFFSET, regval | RP2040_SPI_SSPCR1_SSE);

  for (int i = 0; i < 8; i++)
  {
     spi_getreg(base, RP2040_SPI_SSPDR_OFFSET);
  }
}


static void rp2040_spi_set_cs(struct spi_device *spi, bool enable)
{
    spi_gpio_set_cs(spi, enable);
}


static void rp2040_spi_setup(struct spi_device *spi)
{

}



static int rp2040_spi_transfer(struct spi_device *spi, struct spi_transfer *xfer)
{
    uint32_t spi_base = (uint32_t)spi->master->io_base;
    uint8_t *tx_buf = (uint8_t*)xfer->tx_buf;
    uint8_t *rx_buf = (uint8_t*)xfer->rx_buf;
    uint32_t len = xfer->len;
    register uint32_t regval;

    rp2040_spi_set_cs(spi, true);

    for(int i = 0; i < len; i++) {

        /* Wait while the TX FIFO is full */

        while (!(spi_getreg(spi_base, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_TNF)) ;
    
        /* Write the byte to the TX FIFO */
    
        spi_putreg(spi_base, RP2040_SPI_SSPDR_OFFSET, *tx_buf);
        tx_buf ++;

        /* Wait for the RX FIFO not empty */
    
        while (!(spi_getreg(spi_base, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE))
        ;
    
        /* Get the value from the RX FIFO and return it */
    
        spi_getreg(spi_base, RP2040_SPI_SSPDR_OFFSET);
    }

    rp2040_spi_set_cs(spi, false);
}


static int rp2040_spi_driver_probe(struct platform_device *dev)
{
    fdt_node_t *spi_node= dev->device.dt_node;
    struct spi_gpio gpio_pin;

    // const char *spi_clock = fdt_read_prop_string(spi_node, "clock-bus");
    // if(spi_clock == NULL) {
    //     return -1;
    // }

    // size_t clock_bit = -1;
    // if(fdt_read_prop_int(spi_node, "clock-bit", &clock_bit)) {
    //     PLATFORM_SYSLOG_ERROR("spi: no clock-bit property");
    //     return -1;
    // }

    //clock_enable_by_name(spi_clock, clock_bit);

    if(spi_master_get_gpio_from_fdt(spi_node, &gpio_pin)) {
        return -1;
    }
    
    size_t irq_num = 0;
    if(fdt_read_prop_int(spi_node, "interrupts", &irq_num)) {
        PLATFORM_SYSLOG_INFO("spi: no interrupts property");
    }

    struct spi_master *master = kmalloc(sizeof(struct spi_master));
    if(master == NULL) {
        PLATFORM_SYSLOG_ERROR("spi: kmalloc failed");
        return -1;
    }

    master->io_base = dev->io_base;
    master->set_cs = rp2040_spi_set_cs;
    master->setup = rp2040_spi_setup;
    master->transfer = rp2040_spi_transfer;
    master->pin = gpio_pin;

    platform_set_drvdata(dev, master);

    rp2040_spibus_initialize((uint32_t)master->io_base);
    spi_setbits(master->io_base, 8);
    spi_setmode(master->io_base, 3);

    spi_send((uint32_t)master->io_base, 0xAA);
    spi_send((uint32_t)master->io_base, 0xAA);
    spi_send((uint32_t)master->io_base, 0xAA);
    spi_send((uint32_t)master->io_base, 0xAA);

    SYSLOG_INFO("spi: send 0xAA");

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(spi_node, child) {

        struct spi_device *spi_dev = kmalloc(sizeof(struct spi_device));
        if(spi_dev == NULL) {
            PLATFORM_SYSLOG_ERROR("spi: kmalloc failed");
            return -1;
        }

        if(spi_device_get_config_from_fdt(child, spi_dev)) {
            kfree(spi_dev);
            return -1;
        }

        if(spi_device_register(spi_dev)) {
            PLATFORM_SYSLOG_ERROR("spi: spi_device_register failed");
            return -1;
        }

        spi_dev->master = master;

        if(irq_num) {
            spi_dev->irq_num = irq_num;
        }
    }

    uint8_t tx_buf[20] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
    uint8_t rx_buf[20] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};

    return 0;
}


static void rp2040_spi_driver_remove(struct platform_device *dev)
{
    
}


static struct of_device_id rp2040_spi_driver_match[] = {
    {.compatible = "rp2040-spi", .data = "rp2040-spi" },
    { }
};


static struct platform_driver rp2040_spi_pdrv = {
    .driver = {
        .name = "rp2040-spi",
        .of_match_table = rp2040_spi_driver_match,
    },
    .probe = rp2040_spi_driver_probe,
    .remove = rp2040_spi_driver_remove,
};


static int rp2040_spi_driver_register(void)
{
    return platform_driver_register(&rp2040_spi_pdrv);
}


driver_initcall(rp2040_spi_driver_register);
